home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992 June: ROMin Holiday / ADC Developer CD (1992-06) (''ROMin Holiday'')_iso / Developer Connection - 06-1992.iso / Periodicals / develop / develop 5 code / Lisp Mini-App / All about the Mini-App.text < prev    next >
Encoding:
Text File  |  1992-04-08  |  43.6 KB  |  1,138 lines  |  [TEXT/MWII]

  1. all About the minilisp application
  2.  
  3. Ruben Kleiman
  4.  
  5.  
  6. Minor editing by slm 3/10/92.
  7.  
  8.  
  9. If you're really interested in the nitty-gritty 
  10. details of developing a program using MCL, 
  11. this is your opportunity to learn by doing. 
  12. We'll describe here how to build key features 
  13. of the sample program Mini-Application. The 
  14. complete source code for this program is also 
  15. contained on this disc.
  16.  
  17. Our sample application combines some of the features 
  18. of programs like MacDraw and HyperCard. Its main 
  19. components are:
  20.  
  21. • Windows in which the user can build 
  22. graphics
  23.  
  24. • Palettes from which tools can be selected 
  25. and from which graphic objects can be 
  26. dragged out
  27.  
  28. • A variety of graphic objects (for example, 
  29. buttons, fields) that can be dragged from 
  30. the palettes to build something in the 
  31. windows
  32.  
  33. • Menus to simplify the user interface
  34.  
  35. • An event system that approximates 
  36. HyperCard’s, including the ability to write 
  37. scripts for objects - albeit in Lisp
  38.  
  39. You may want to run the application before learning 
  40. how it was built.  Instructions follow in "To Run the 
  41. Application."  Starting with the section "Creating the 
  42. Framework," we show how to build the application in a 
  43. top-down manner, paying attention to the features of 
  44. the development environment and of Lisp that enhance 
  45. programmer productivity (and enjoyment). We craft the 
  46. key methods or functions for each aspect of the 
  47. application. We don't provide a complete exegesis of 
  48. the application, but  touch on every component 
  49. mentioned above, implementing the main code for that 
  50. feature. 
  51.  
  52. to run the application
  53.  
  54. To run the application, you need a copy of Macintosh 
  55. Common Lisp, Version 2.0, available from 
  56. APDA. MCL runs in 4 megabytes.
  57.  
  58. To run the application, open the file Read Me First.lisp 
  59. into a text window in MCL and read the instructions. 
  60. All you have to do is to set up the name of the 
  61. directory where you installed the Mini-Application 
  62. folder supplied on the CD ROM and evaluate the text 
  63. window.  This will cause the application to load and 
  64. run.
  65.  
  66. The application consists of the following files:
  67.  
  68. File Name                Description
  69.  
  70. Read Me First.lisp            The code that loads and runs         
  71.                         the application. It also initializes         
  72.                         objects needed by each run.
  73.  
  74. mini-application-loader.lisp        Loads the application code.
  75.  
  76. compile-mini-application.lisp        A file which, when loaded,
  77.                      recompiles the application
  78.  
  79. globals.lisp                Declares all globals used in the
  80.                      application.
  81.  
  82. draw-dialog-class.lisp            The class of windows and all
  83.                      their methods.
  84.  
  85. palette-class.lisp            The class of palettes and all         
  86.                         their methods.
  87.  
  88. draw-item-class.lisp            The classes of all graphic
  89.                      objects and all their methods.
  90.  
  91. menus.lisp                Defines the menus and menu         
  92.                         items used by this application,             
  93.                             as well as the functions they                 
  94.                                 invoke.
  95.  
  96. default-handlers.lisp            Defines the default behavior of
  97.                      the HyperCard-like script
  98.                          handlers, like MouseDown.
  99.  
  100. utilities.lisp                Utility functions used             
  101.                         throughout.
  102.  
  103. resources                Contains a few resources (for
  104.                     example, icons).
  105.  
  106.  
  107. Creating The Framework
  108.  
  109. As we begin to write our application, we open a MCL 
  110. text document in which to enter and save the code. To 
  111. do this, go to the File menu and choose New. A 
  112. document called New will be opened. 
  113.  
  114. We will run our application by calling the function 
  115. start-application, which will bring up the necessary 
  116. menus and display a greeting. Type the following into 
  117. our empty document:
  118.  
  119. (defun start-application ()
  120.   (when (welcome-accepted?) ; Display welcome message
  121.     (show-menus)            ; Initialize the menubar
  122.     (print “Welcome!”)))    ; Print greeting
  123.  
  124. You can begin each line with a tab, and the editor will 
  125. automatically indent your code to the appropriate 
  126. level. The defun function is how you define a function. 
  127. This consists of a function name (first argument), a 
  128. list that specifies the arguments assumed by the 
  129. function (second argument, also known as the “lambda 
  130. list”), and any number of Lisp expressions (rest of 
  131. arguments, also known as the body). Evaluating the 
  132. function returns the value of the last expression in the 
  133. body. In start-application, the lambda list has nothing 
  134. in it, so the function does not take any arguments. The 
  135. body consists of a conditional expression that calls 
  136. the function welcome-accepted? and calls show-
  137. menus if that function returns a value other than NIL. 
  138. All lines beginning with a semicolon are comments.
  139.  
  140. To compile start-application, select the definition 
  141. with the mouse and choose Eval Selection from the 
  142. Eval menu. The compiler will print out a warning in the 
  143. Listener to the effect that welcome-accepted? and 
  144. show-menus are undefined. Lisp was tolerant:  it 
  145. could have given you an error!  Don’t worry about this: 
  146. when you define those functions, everything will be ok. 
  147. Now save your text by choosing Save As... from the File 
  148. menu.
  149.  
  150. Our welcome dialog will display a string identifying 
  151. the application and give the user an opportunity to 
  152. cancel. This is straightforward with the predefined 
  153. function y-or-n-dialog. Enter the following definition 
  154. into your text window:
  155.  
  156. (defun welcome-accepted? ()
  157.   (y-or-n-dialog "Welcome To My App. Ready, Set, Go?"
  158.                  :yes-text  "Go Dude!"
  159.                  :no-text   "Nope"))
  160.  
  161. y-or-n-dialog accepts as its first argument our 
  162. message, and optional keyword arguments called :yes-
  163. text and :no-text are assigned the values “Go Dude!” 
  164. and "Nope," respectively. The latter are the text 
  165. assigned to buttons on the welcome dialog. Since the 
  166. call to y-or-n-dialog does not depend on any 
  167. variables, we can immediately test welcome-
  168. accepted? by selecting the text containing the call to 
  169. y-or-n-dialog and then choosing Eval Selection from 
  170. the Eval menu. The dialog should be shown. If you 
  171. select the Go Dude! button, the function should return T 
  172. (Lisp’s Boolean for true). When you evaluate any 
  173. expression either in the Listener or in a text window, 
  174. the result of the evaluation is returned in the Listener. 
  175. Select the Go Dude! button to see what’s returned.
  176.  
  177. Creating Our Menus
  178.  
  179. The function show-menus is invoked by start-
  180. application when the welcome message is accepted. In 
  181. our application, show-menus will eliminate the 
  182. normal MCL menus and display a menu bar with the 
  183. following menus:  the standard File, Edit, and Window 
  184. menus, an Options menu, and a menu that provides 
  185. information about the currently selected graphic 
  186. object. For purposes of this demonstration, we will 
  187. limit ourselves to building the File menu. Our File 
  188. menu will have New, Close, and Quit menu items (we 
  189. won’t support Save). See the source code for the 
  190. definitions of the Close and Quit menu items.
  191.  
  192. Type and evaluate this expression in a text window:
  193.  
  194. (defvar *mini-application-file-menu*
  195.   (make-instance 'menu :menu-title "File"))
  196.  
  197. First, defvar is the Lisp function that lets you define 
  198. and initialize a global variable:  we have thus 
  199. initialized the variable *mini-application-file-menu* 
  200. to contain the menu instance. The method make-
  201. instance, which is similar to New in SmallTalk and in 
  202. MacApp, enables you to create an instance from a 
  203. class, and to optionally initialize some of its slots. 
  204. (The term slot is synonymous with instance variable in 
  205. other object-based languages.) The first argument to 
  206. make-instance is the name of the class, and the 
  207. subsequent arguments are keyword-value pairs with 
  208. optional initializations that depend on the class. 
  209. Above, the predefined MCL class menu is instantiated 
  210. and the :menu-title keyword option is initialized with 
  211. the name of the menu. (The name menu is preceded 
  212. with a single quotation mark to indicate that we are 
  213. referring to the symbol menu rather than to the 
  214. variable menu.)  
  215.  
  216. To test this instance, you can invoke the menu-install 
  217. method on it:
  218.  
  219. (menu-install *mini-application-file-menu*)
  220.  
  221. The menu bar should now include a new File menu on 
  222. the far right. Clicking on the new File menu will not 
  223. yield any menu items, so let’s define one. Evaluate the 
  224. following:
  225.  
  226. (defvar *file-new-menu-item*
  227.   (make-instance 'menu-item
  228.     :menu-item-title "New"
  229.     :command-key #\N
  230.     :menu-item-action 'new-menu-item-action))
  231.  
  232. The predefined MCL class menu-item supports, 
  233. among other things, an optional command-key 
  234. character - characters in Lisp are represented by 
  235. prefixing the character with #\ - and a menu item 
  236. action. The latter is a function that will be called 
  237. when the menu item is chosen. In this example, we 
  238. define the keystroke combination Command-N to be 
  239. equivalent to choosing the menu item. We have also 
  240. given new-menu-item-action as the name of the 
  241. function that should be called when this menu item is 
  242. chosen - new-menu-item-action is defined in the 
  243. section “Creating Our Window”. The additional 
  244. keywords :disabled, :menu-item-color, :menu-item-
  245. checked, and :style permit you to specify whether the 
  246. menu item should be disabled, the colors of the parts 
  247. of the menu (such as background, text), whether the 
  248. item should be checked, and its style.
  249.  
  250. We are ready to install the new menu item on the File 
  251. menu. Type and evaluate the following in a text 
  252. window:
  253.  
  254. (add-menu-items *mini-application-file-menu*
  255.                 *file-new-menu-item*)
  256.  
  257. add-menu-items is a predefined MCL method for 
  258. menus that enables us to add one or more menu items 
  259. to a menu—whether the menu is installed or not. If you 
  260. pull down the new File menu, you should see the New 
  261. item. However, if you choose this item, you should see 
  262. an error report in the Listener telling you that new-
  263. menu-item-action is undefined. Lisp is forgiving but 
  264. not clairvoyant!
  265.  
  266. Now that we know how to define menus and menu 
  267. items, we want to define a menu bar so that we can 
  268. treat our application’s menus as a unit. First, let’s 
  269. deinstall our File menu by evaluating the following:
  270.  
  271. (menu-deinstall *mini-application-file-menu*)
  272.  
  273. A menu bar consists of a list of menus that will be 
  274. displayed in a left-to-right order. The complete source 
  275. code for this application defines all menus and menu 
  276. items, but here we will restrict ourselves to the File 
  277. and Windows menus. We define the menu bar by 
  278. evaluating this expression:
  279.  
  280. (defvar *mini-application-menubar*
  281.   (list *mini-application-file-menu*
  282.         *windows-menu*)))
  283.  
  284. The convenient variable *windows-menu* is bound by 
  285. MCL to the menu that MCL already uses in its menu 
  286. bar, so we add it here to get that functionality for 
  287. free. To install our menu bar, evaluate this:
  288.  
  289. (set-menubar *mini-application-menubar*)
  290.  
  291. set-menubar deinstalls the MCL menubar and 
  292. installs ours in its place. MCL provides a set of 
  293. methods that can be invoked at any time on menus or 
  294. menu items. For example, examine the New item after 
  295. evaluating each of  the following lines:
  296.  
  297. (set-menu-item-check-mark *file-new-menu-item* t) ; Put a check mark
  298.  
  299. (set-menu-item-style *file-new-menu-item* :outline) ; Outline font
  300.  
  301. (set-menu-item-title *file-new-menu-item* "Was New") ; Other title
  302.  
  303. (menu-item-disable *file-new-menu-item*)  ; Disable the menu item
  304.  
  305. MCL binds its own menu bar to the global *default-
  306. menubar* (never leave home without it!), so to get 
  307. back to MCL’s menu bar, evaluate:
  308.  
  309. (set-menubar *default-menubar*)
  310.  
  311. Finally, we want to define the function show-menus, 
  312. which will be called by start-application when our 
  313. application needs to be started. This is simple:
  314.  
  315. (defun show-menus ()
  316.   (set-menubar *mini-application-menubar*))
  317.  
  318. The complete source code defines *mini-application-
  319. menubar* to include all of the necessary menus and 
  320. menu items to run the application.
  321.  
  322. Creating Our Window
  323.  
  324. To see how windows are handled, let’s start right off 
  325. by creating one. Evaluate:
  326.  
  327. (setq my-window (make-instance 'window))
  328.  
  329. You should immediately see a window named Untitled 
  330. appear on your main screen . Let’s see what we can do 
  331. with windows. Examine the window after evaluating 
  332. each of the following lines:
  333.  
  334. (set-window-title my-window "d e v e l o p") ; Change window title
  335.  
  336. (set-view-size my-window 200 200) ; Change window size
  337.  
  338. (set-view-position my-window 150 50) ; Change window position
  339.  
  340. set-window-title, set-view-size, and other functions 
  341. used here and in other places are actually methods:  
  342. they are sensitive to the types of objects being passed 
  343. to them as arguments. As with other object-based 
  344. languages, Lisp methods decide what to do based on the 
  345. class of which the object is an instance. Unlike other 
  346. object-based systems that only look at the class of 
  347. the first argument, the Common Lisp Object System 
  348. (CLOS) considers the classes of all of its arguments 
  349. before deciding what to do. It is arguable just how 
  350. useful this really is, but if you ever need this 
  351. capability, it’s there! CLOS is implemented such that 
  352. you won’t pay for this feature unless you use it. 
  353. Methods are defined using the function defmethod, 
  354. while regular functions are defined using defun.
  355.  
  356. In our section on menus and in this section, we’ve seen 
  357. that MCL uses the CLOS system to implement its user 
  358. interface with a variety of methods — for example, add-
  359. menu-items, set-view-position. Fortunately, these 
  360. methods are also available to us, the users. One other 
  361. benefit of the MCL user interface is its view 
  362. architecture and, in addition to windows, a useful 
  363. variety of predefined views like radio buttons, check 
  364. boxes, and editable text.  Let’s understand MCL’s view 
  365. system so that we can exploit it for our application.
  366.  
  367. The view architecture allows one to define graphic 
  368. objects as views. A view has its own coordinate 
  369. system and origin relative to a view that contains it 
  370. (called the view container); it is defined as a 
  371. rectangular area determined by its position relative to 
  372. its container and its size. It is thus easy to, for 
  373. example, independently scroll separate views. A view 
  374. can be a window, a radio button, or anything that can 
  375. be displayed on the screen.
  376.  
  377. We will build our application windows with the help of 
  378. the view system. However, we will retain some control 
  379. over how views are displayed. For example, we want to 
  380. control the back-to-front ordering of graphic objects 
  381. in a window, to be able to move objects among 
  382. windows, and to constrain the behavior of objects 
  383. (such as tools) in palettes.
  384.  
  385. Let’s create a subclass of the predefined window 
  386. class that is customized for our application. This class 
  387. will include a slot my-items that will hold a list of 
  388. all the graphic objects that we draw into the window, 
  389. remembering their back-to-front ordering. We start by 
  390. creating a subclass of the window class named draw-
  391. dialog. defclass is the CLOS function for defining a 
  392. class:
  393.  
  394. (defclass draw-dialog (window)
  395.   ((my-items :initform NIL)                ; List of all items in window
  396.    (item-last-under-mouse :initform NIL)   ; Item currently under the mouse
  397.    (browse-mode :initform nil)             ; Mode in which window is being used (default = author)
  398.    (selections :initform nil)              ; Currently selected item(s)
  399.    (create-by-rectangle :initform nil)     ; Can draw-items be created by dragging out a rectangle?
  400.    )
  401.   (:documentation "This class defines our windows"))
  402.  
  403. Our class draw-dialog adds five slots named my-items,
  404. browse-mode, item-last-under-mouse, selections and
  405. create-by-rectangle to its superclass, window. The first
  406. argument to defclass is the name of the class, draw-dialog, 
  407. followed by a list with the names of all classes, if 
  408. any, from which we want to inherit (that is, that we 
  409. want our class to be a subclass of)—in our case, just 
  410. the window class. Multiple inheritance is supported 
  411. and although things are, by default, inherited in the 
  412. order in which they appear in this list, protocols are 
  413. available in CLOS for controlling this. The third and 
  414. fourth arguments shown here are a list of descriptors 
  415. for each new slot that we want to define and 
  416. documentation text, respectively.
  417.  
  418. A descriptor is a list that starts with the name of the 
  419. slot followed by a set of keyword-value pairs that 
  420. represent options concerning how the slots get 
  421. initialized when an  instance of this class is created. 
  422. We used the :initform keyword followed by the 
  423. expression NIL. This means that when an instance of 
  424. draw-dialog is created, the my-items slot will by 
  425. default be set to whatever the following expression 
  426. evaluates to—that is, NIL. 
  427.  
  428. The draw-dialog class will inherit slots from the 
  429. window class and from the classes it inherits from. 
  430. Verify that this new subclass definition works by 
  431. creating an instance of it:
  432.  
  433. (setq my-window (make-instance 'draw-dialog))
  434.  
  435. One way to check whether our new slots my-items, 
  436. browse-mode, item-last-under-mouse have, selections and
  437. create-by-rectangle been added and initialized to the 
  438. value NIL, is to get their slot values directly. These 
  439. values can be obtained by evaluating the following 
  440. expressions:
  441.  
  442. (slot-value my-window 'my-items)  ; Returns value of slot my-items
  443. (slot-value my-window 'browse-mode)
  444. (slot-value my-window 'item-last-under-mouse)
  445. (slot-value my-window 'selections)
  446. (slot-value my-window 'create-by-rectangle)
  447.  
  448. Another way to check these slots is by using the Lisp 
  449. inspector to view the object instance. The inspector 
  450. provides us with a description of any object (this 
  451. includes constants, variables, and functions) that we 
  452. want to look at. (In addition, as of this writing, the 
  453. released version of the MCL 2.0 inspector will allow 
  454. you to directly edit values.) To inspect the instance in 
  455. my-window, evaluate (inspect my-window), which 
  456. brings up a screen that looks like Figure 1.
  457.  
  458. Figure 1: [ See Figure 3 in develop, p104 ]
  459.  
  460. Viewing an Instance of the draw-dialog Class in the 
  461. Inspector
  462.  
  463. The items following Local slots: are slots of the 
  464. object bound to my-window. The slots that we added 
  465. should be at the top, as shown; the remaining slots 
  466. have been inherited from the window class. For 
  467. example, the slot wptr contains the pointer to the 
  468. Macintosh window definition block, and view-size is 
  469. the point (that is, a long used as a point) for the 
  470. window’s size. You should resist the temptation to 
  471. directly access these slots, unless you absolutely need 
  472. to. The predefined window methods, like set-view-
  473. size, should be used instead. If you’d like to inspect 
  474. the window block itself, you can double-click on the 
  475. line in Figure 1 with the wptr to get the display shown 
  476. in Figure 2.
  477.  
  478. Figure 2: [ See Figure 4 in develop, p105 ]
  479.  
  480. Viewing the Window Record for Our Instance of 
  481. draw-dialog in the Inspector
  482.  
  483. You could continue this process indefinitely—for 
  484. example, looking next at the rectangle records in the 
  485. window record. As you look at these lower-level 
  486. structures, you begin to see that although Lisp is a 
  487. very high-level language, you can still access the 
  488. system as if writing in assembler language. As we 
  489. shall see later, this clarifies toolbox access 
  490. considerably when compared with C and Pascal.
  491.  
  492. Now, we want to define the behavior of our 
  493. window—what happens when it is created, when it is 
  494. closed, when things are added to it, when the mouse is 
  495. clicked on it, and so on. Fortunately, much of this 
  496. behavior is already handled for us by the predefined 
  497. window methods. 
  498.  
  499. In our menu section above, we defined for the File 
  500. menu the New menu item which, when chosen, called 
  501. the function new-menu-item-action. We are now 
  502. ready to define this function, which creates an 
  503. instance of a draw-dialog window:
  504.  
  505. (defvar *window-count* 0)   ; For a new window’s title
  506.  
  507. (defun new-menu-item-action ()
  508.   (make-instance 'draw-dialog
  509.     :window-title
  510.     (format nil "Draw Dialog ~a" (incf *window-count*))
  511.     :view-size (make-point (- *screen-width* 150)
  512.                            (- *screen-height* 100))
  513.     :view-position (make-point 5 40)
  514.     :color-p *color-available*
  515.     :window-type :document-with-zoom))
  516.  
  517. We used the Lisp format statement to create the 
  518. window title. format is a sophisticated instrument 
  519. not only for writing strings to any stream, but also for 
  520. generating string expressions of arbitrary complexity. 
  521. We use the :window-type option to require a 
  522. document with zoom box. *screen-width* and 
  523. *screen-height* are the main screen’s width and 
  524. height, respectively, which are supplied by MCL. 
  525. Finally, incf is a macro that lets us increment and set 
  526. the value of *window-count*.
  527.  
  528. One interesting behavior that we want to add to draw-
  529. dialog is to make the window handle events like 
  530. HyperCard does. The concept is that an instance of 
  531. draw-dialog, as well as any object in it, can be 
  532. scripted. A script will here be defined as a set of 
  533. handlers. Instead of having a scripting language like 
  534. HyperTalk, we’ll use Lisp, which is handier. A handler 
  535. will be a method whose name is the name of the 
  536. handler. The following types of handlers will therefore 
  537. be defined:
  538.  
  539. idle            Periodically called by the system.
  540.  
  541. mouse-enter            Called whenever the mouse enters the
  542.             bounds of the object.
  543.  
  544. mouse-leave            Called whenever the mouse leaves the
  545.              bounds of the object.
  546.  
  547. mouse-within            Periodically called while the mouse is 
  548.             within the bounds of the object.
  549.  
  550. key            Called whenever the object is selected 
  551.             and a key is pressed.
  552.  
  553. mouse-down            Called whenever the mouse goes down 
  554.             within the bounds of the object.
  555.  
  556. mouse-up        Called whenever the mouse goes up 
  557.             within the bounds of the object.
  558.  
  559. All objects will have default handlers that do nothing, 
  560. except for the window’s idle handler, which simply 
  561. passes the idle event to objects in its window.
  562.  
  563. We’ll define a set of default handlers that will be 
  564. called if the user does not write any scripts for these 
  565. handlers. Most of these will do nothing. Here, we 
  566. implement the idle default handler for the draw-
  567. dialog class:
  568.  
  569. (defmethod idle ((this-window draw-dialog))
  570.   (dolist (item (slot-value this-window 'my-items))
  571.     (idle item)))
  572.  
  573. This method simply sends an idle message to each item 
  574. in the my-item slot of the window. (Note how easily 
  575. we have been able not only to talk about but also to 
  576. show how we can control the items in our drag-dialog 
  577. windows without having defined the objects 
  578. themselves! This will be more dramatically clear as 
  579. we proceed to provide the event-handling machinery 
  580. that drives all of the events above.)
  581.  
  582. This is a simple method definition. Its name is idle and 
  583. it has only one formal argument: an instance of a 
  584. draw-dialog class. During the execution of the 
  585. method, this instance is bound to the local variable 
  586. this-window. In many other object-based languages, 
  587. this variable is called self, but in Common Lisp it must 
  588. be explicitly bound to a variable in the formal 
  589. argument list. Why? Because, as we mentioned earlier, 
  590. a Common Lisp method will look at the class of every 
  591. argument passed to it before deciding which function 
  592. to call. Thus, there isn’t any distinguished self, but 
  593. rather many selves competing for identityhood!
  594.  
  595. Another thing to notice in idle is how a slot of the 
  596. object is accessed. The construct
  597.  
  598. (slot-value <object> <slot-name>)
  599.  
  600. is the default way to refer to an object’s slot value. 
  601. However, the :accessor option of defclass enables you 
  602. to define how you want to name the slot accessor. For 
  603. example:
  604.  
  605. (defclass person
  606.   ((age :initform 20
  607.         :accessor age)))
  608.  
  609. enables you to access the age slot of an instance x of 
  610. person via the expression (age x). Named accessors 
  611. have the virtue of hiding the object system and are 
  612. encouraged. However, we will consistently use slot-
  613. value to access slot values in order to clarify our use 
  614. of object slots in our tutorial.
  615.  
  616. Next, we want to drive the event system. From where? 
  617. Unlike in other Macintosh programming environments, 
  618. when we write a Lisp program we are not responsible 
  619. for calling WaitNextEvent. Fortunately, the MCL 
  620. window class has a predefined method called 
  621. window-null-event-handler that is periodically 
  622. called by MCL; in particular, after each 
  623. WaitNextEvent is processed. MCL will apply this 
  624. method to any active instance of a window class of 
  625. which it is aware. This includes all instances of 
  626. draw-dialog, since the latter is a subclass of 
  627. window, giving us one of the hooks we needed. Let’s 
  628. define window-null-event-handler for draw-dialog 
  629. to handle the event traffic as follows:
  630.  
  631. (defmethod window-null-event-handler ((w draw-dialog))
  632.   (let* ((where (view-mouse-position w))   ; Window coordinate point of mouse
  633.          (item (find-view-containing-point w (point-h where) (point-v where)))
  634.          (last-under-mouse (slot-value w 'item-last-under-mouse)))
  635.     ;; Handle mouse-within, mouse-enter and mouse-leave events
  636.     (when (and (slot-value w 'browse-mode)    ; in browser mode and
  637.                item)                          ; when mouse is over an item
  638.       (cond ((eq last-under-mouse item)
  639.              (mouse-within item where))
  640.             (t (if last-under-mouse
  641.                  (mouse-leave last-under-mouse where))
  642.                (setf (slot-value w 'item-last-under-mouse) item)
  643.                (mouse-enter item where)))
  644.       ;; Handle idle event for window
  645.       (idle w)))
  646.   (call-next-method))
  647.  
  648. Here, we bound the variable w to the instance of the 
  649. draw-dialog window to which the message was sent. 
  650. The let* statement creates the following local 
  651. variables:
  652.  
  653. where    The current location of the mouse as a
  654.     point.
  655.  
  656. item    The item (either the window or an item
  657.     in the window) under the mouse.
  658.  
  659. last-under-mouse
  660.     The item that was under the mouse
  661.     last time we checked.
  662.  
  663. The MCL method view-mouse-position applied to our 
  664. window returns to us the position of the mouse in 
  665. local window coordinates. The MCL method find-
  666. view-containing-point returns the most specific 
  667. view under the mouse: either an item in the window, or 
  668. the window itself if the mouse isn’t over an item. 
  669. Since we plan to treat our draw items as views (that 
  670. is, as subviews of the window they are contained in), 
  671. we will for now let find-view-containing-point do 
  672. the work for us. Finally, the item-last-under-mouse 
  673. slot is extracted here for performance since we’ll 
  674. make multiple references to it.
  675.  
  676. The cond statement figures out which of and to whom 
  677. the following messages should be sent: mouse-enter, 
  678. mouse-within, or mouse-leave. We check the value of 
  679. the window’s slot browse-mode to see if the window 
  680. is in browse or author mode. We only want to dispatch 
  681. the events when in browse mode.
  682.  
  683. Note how the slot item-last-under-mouse is set in 
  684. that conditional. The general expression for setting a 
  685. slot value is:
  686.  
  687. (setf (slot-value <object> <slot-name>) <value>)
  688.  
  689. The setf function is a generalization of setq and can 
  690. therefore be used in its place. setf enables you to 
  691. make an expression evaluate to the value you want. 
  692. More precisely, evaluating
  693.  
  694. (setf <list-or-symbol> <value>)
  695.  
  696. implies that evaluating <list-or-symbol> will return 
  697. <value>. For symbols, this is always the case. For 
  698. lists, this is only possible for certain system-defined 
  699. functions, like slot-value. But you are free to define 
  700. any function to behave this way: consult define-setf-
  701. method in your friendliest Common Lisp book.
  702.  
  703. Next to last, we call the idle method in the window. We 
  704. have defined this method above: it distributes the idle 
  705. event to all items in it. Finally, we call call-next-
  706. method, which is the way CLOS enables us to call the 
  707. window-null-event-handler method that draw-
  708. dialog would otherwise have inherited from the class 
  709. window. If we wanted to override it completely, we 
  710. would not have called call-next-method. The latter is 
  711. similar to MacApp’s inherit- or SmallTalk’s CallSuper 
  712. methods.
  713.  
  714. We’re only left with mouse-down, mouse-up, and 
  715. key events to worry about. We’re lucky again, because 
  716. MCL is always busy in the background dispatching 
  717. calls to the MCL methods view-click-event-handler, 
  718. window-mouse-up-event-handler, and view-key-
  719. event-handler, which are called after the mouse goes 
  720. down or up, or after a keystroke, respectively. These 
  721. methods are a breeze to code:
  722.  
  723. (defmethod view-click-event-handler ((w draw-dialog) where)
  724.   (let ((item (find-view-containing-point 
  725.                w (point-h where) (point-v where))))
  726.     (if (slot-value w 'browse-mode)
  727.       (if item
  728.         (mouse-down item where))
  729.       (author-mode-click-handler item where))))
  730.  
  731. (defmethod window-mouse-up-event-handler ((w draw-dialog))
  732.   (let* ((where (view-mouse-position w))
  733.          (item (find-view-containing-point
  734.                 w (point-h where) (point-v where))))
  735.     (if (and item (slot-value w 'browse-mode))
  736.       (mouse-up item where)))
  737.   (call-next-method))
  738.  
  739. (defmethod view-key-event-handler ((w draw-dialog) character)
  740.   (let* ((where (view-mouse-position w))
  741.          (item (find-view-containing-point
  742.                 w (point-h where) (point-v where))))
  743.     (if (and item (slot-value w 'browse-mode))
  744.       (key item character)))
  745.   (call-next-method))
  746.  
  747. The mouse position is supplied by MCL in view-click-
  748. event-handler, which is called when the mouse is 
  749. pressed down; in the other cases we find it using 
  750. view-mouse-position. For the view-key-event-
  751. handler MCL passes the character for the key that 
  752. was pressed. Again, we call call-next-method in case 
  753. it does anything important. The method author-mode-
  754. click-handler called by view-click-event-handler 
  755. will take care of object selection, deselection, 
  756. resizing, and dragging operations similarly to 
  757. HyperCard. Note that this method is invoked on the 
  758. item, which could be the window itself or one of the 
  759. graphic items in it. The latter version is described in 
  760. the section “Creating Draw Items,” while the 
  761. definition for the window version is:
  762.  
  763. (defmethod author-mode-click-handler ((w draw-dialog) where)
  764.   (if (double-click-p)
  765.     (author-mode-double-click-handler w where)
  766.     (author-mode-single-click-handler w where)))
  767.  
  768. double-click-p is a MCL function that tells us 
  769. whether we have a double click. The method author-
  770. mode-double-click-handler (not shown in this 
  771. article) will be responsible for presenting a dialog box 
  772. with information about the window, while:
  773.  
  774. (defmethod author-mode-single-click-handler ((w draw-dialog) where)
  775.   (when (eq w (find-view-containing-point
  776.                w (point-h where) (point-v where))
  777.             (deselect-items w)))
  778.  
  779. The latter method will deselect everything when the 
  780. mouse is clicked over the window and there are no 
  781. items under it. The method deselect-items is 
  782. straightforward:
  783.  
  784. (defmethod deselect-items ((window draw-dialog))
  785.   (dolist (item (slot-value window 'selections))
  786.     (setf (slot-value item 'selected) nil)
  787.     (view-draw-contents item))         ; Redraw item
  788.   (setf (slot-value window 'selections) nil))
  789.  
  790. We cycle through each item that has been put into the 
  791. selections slot of the window and turn off its 
  792. selected flag (see the “Creating Draw Items” section), 
  793. redraw the item, and then delete the selections slot.
  794.  
  795. As we define more functions and methods, our text 
  796. windows are becoming rather large. Fortunately, we 
  797. can go to the MCL Toolsmenu and choose List 
  798. Definitions to get the window shown in Figure 3. Each 
  799. line in this window shows a function, method, and 
  800. other kinds of definitions found in the text window. By 
  801. double-clicking on any item, you can scroll the text to 
  802. the right item. It is possible to list things 
  803. alphabetically or in the order they appear in the 
  804. window. One of these windows can be simultaneously 
  805. opened per opened text window.
  806.  
  807. Figure 3: [ See Figure 2 in develop, p98 ]
  808.  
  809. The "List Definitions" Window
  810.  
  811. We are done with the window class, except for a 
  812. detail concerning find-view-containing-point. We 
  813. would like it to take into account the back-to-front 
  814. ordering of items in our window, but the view system 
  815. will not necessarily be aware of it. Therefore, we will 
  816. override this method by our own version, which will 
  817. cycle through the items in the my-values slot of the 
  818. draw-dialog class in the correct front-to-back order.
  819. [ Don't look for a definition of find-view-containing-point,
  820.   you won't find it. The built-in version is used. It works
  821.   fine. ]
  822.  
  823. Creating Our Palette
  824.  
  825. Our palette will have two kinds of objects in it:
  826.  
  827. • Tools to select what should be done
  828.  
  829. • Objects that can be dragged into our windows
  830.  
  831. It seems that the draw-dialog class can do most of 
  832. the work for us, so we will define a subclass of it 
  833. called palette as follows:
  834.  
  835. (defclass palette (draw-dialog)
  836.   ((my-tools      :initarg :tools)
  837.    (my-draw-items :initarg :draw-items))
  838.   (:documentation "Palettes used in our application"))
  839.  
  840. The my-tools slot will contain all the items in the 
  841. palette that can be viewed as tools, whereas the my-
  842. draw-items slot will contain all items that can be 
  843. dragged out of the palette into other windows. These 
  844. slots will be initialized with instances of tools and 
  845. graphic items that will be used in any one session of 
  846. our application. Once a palette is created and 
  847. initialized, a layout method (in CD ROM sources) will 
  848. look at these slots to figure out how to lay out the 
  849. items—for example, tools first and draggable items 
  850. next. These are convenience slots, since (as we’ll see) 
  851. graphic items themselves know whether they are tools 
  852. or not.
  853.  
  854. We have to enforce the following differences between 
  855. our palette and draw-dialog classes:
  856.  
  857. • Since tools are not accessible to users, a 
  858.   convenient place to put the code that does 
  859.   the tool’s work when it is clicked is the 
  860.   tool’s mouse-down event handler.
  861.  
  862. • Items in our palette cannot be moved 
  863.   within or resized in the palette.
  864.  
  865. • Tools cannot be dragged out of the palette.
  866.  
  867. First, we want to make sure that a palette’s tools get 
  868. the mouse-down event whether or not we are in 
  869. author mode. We can redefine view-click-event-
  870. handler this way:
  871.  
  872. (defmethod view-click-event-handler ((palette palette) where)
  873.   (let ((item (find-view-containing-point
  874.                palette (point-h where) (point-v where))))
  875.     (if (slot-value item 'tool)
  876.       (mouse-down item where))   ; dispatch the mouse-down event
  877.     (call-next-method)))         ; proceed with the usual behavior
  878.  
  879. If an item is selected and if it is a tool, we force the 
  880. mouse-down and then call the draw-dialog’s view-
  881. click-event-handler method using call-next-method. 
  882. The check (slot-value item 'tool) anticipates the 
  883. definition for graphic items in the next section: the 
  884. tool slot of an item tells it whether it is a tool or not.
  885.  
  886. Finally, since the resizing and dragging is done by the 
  887. items themselves, items that know themselves to be 
  888. in the palette should not allow themselves to be 
  889. dragged or resized around a palette (but they should let 
  890. themselves be dragged to other windows!). Similarly, 
  891. items that are tools will know better than to drag 
  892. themselves out of a palette! This (and more) we’ll 
  893. discover in the following section.
  894.  
  895. Creating Draw Items
  896.  
  897. This is our last and most substantial section, for the 
  898. graphic items have been delegated most of the work by 
  899. the other classes. We will define one basic class of 
  900. graphic items from which tools and other kinds of user 
  901. interface objects will derive.
  902.  
  903. (defclass draw-item (dialog-item)
  904.   ((rectangle :initarg :rect :initform nil)
  905.    (tool :initarg :tool :initform nil)
  906.    (selected :initform nil) 
  907.    (name :initarg :name :initform ""))
  908.   (:documentation "The user interface objects"))
  909.  
  910. We call our class draw-item. It will be a subclass of 
  911. MCL’s dialog-item class. The latter class supports a 
  912. variety of specializations for typical Macintosh user 
  913. interface items, like radio and round buttons, check 
  914. boxes, and static text. Since we don’t want to 
  915. duplicate that functionality, we subclass from dialog-
  916. item. Since dialog-item itself inherits from the class 
  917. view (actually, from simple-view—but let’s not split 
  918. hairs here), we get all the functionality we need 
  919. without using multiple inheritance!
  920.  
  921. If you are using MCL 2.0 right now, you can verify this 
  922. provenance by inspecting the class object for draw-
  923. item using the inspector. find-class returns to you the 
  924. class object, given a class name:
  925.  
  926. (inspect (find-class 'draw-item))
  927.  
  928. When the inspector window comes up, double-click on 
  929. the line ccl::precedence-list: and you will get the 
  930. window shown in Figure 4.
  931.  
  932. Figure 4: [ See Figure 5 in develop, p108 ]
  933.  
  934. The Class Precedence List for draw-item
  935.  
  936. You will notice that our draw-item not only inherits 
  937. from simple-view, but also from stream. The latter 
  938. class allows one to apply format, print, and other 
  939. stream (input/output) methods to our items!
  940.  
  941. draw-item’s rectangle slot will be used to keep the 
  942. bounds of the draw-item. These bounds will be the 
  943. same as one would obtain by using the methods view-
  944. position and view-size when applied to the item, but 
  945. will be much more efficient than making two method 
  946. calls. The tool slot tells us whether the item is a tool 
  947. or not. The selected slot tells us whether the item is 
  948. selected (note that this information is redundant since 
  949. it is also maintained in the window’s selections slot). 
  950. Finally, a name for the draw item.
  951.  
  952. Now, the business for draw-item to attend to, as we 
  953. recall, is as follows:
  954.  
  955. • Implement the author-mode-click-
  956.   handler method, which is responsible for 
  957.   resizing and dragging an item.
  958.  
  959. • Implement a variety of useful subclasses 
  960.   of draw-items, like editable text fields.
  961.  
  962. As we recall, the author-mode-click-handler method 
  963. was called by the draw-dialog window’s view-click-
  964. event-handler (see “Creating Our Window”) when the 
  965. mouse clicked on an item contained by the window 
  966. when in author mode. Author mode allows us to resize 
  967. and drag items in a window and, in our implementation, 
  968. to even drag items between windows. Let’s define this 
  969. method:
  970.  
  971. (defmethod author-mode-single-click-handler
  972.            ((item draw-item) mouse-loc)
  973.   (if (mouse-moved (view-window item) mouse-loc)
  974.     (if (resize? item mouse-loc)
  975.       (resize item mouse-loc)
  976.       (maybe-drag item mouse-loc)))
  977.   (deselect-items (view-window item))
  978.   (select-item item))
  979.  
  980. (defun mouse-moved (window mouse-loc)
  981.   (loop
  982.     (cond ((not (mouse-down-p))
  983.            (return nil))
  984.           ((neq (view-mouse-position window) mouse-loc)
  985.            (return t)))))
  986.  
  987. The method view-window, when applied to a view, 
  988. returns the window that contains it. First, we call 
  989. mouse-moved to see if the mouse moved before it 
  990. was released: if so, either a resize or drag is intended. 
  991. We call resize? to test whether a resize is intended, 
  992. and if so we call resize to do the resizing, or else we 
  993. call maybe-drag. Why maybe-drag? Because we will 
  994. not want to drag if the item is a tool. Let’s look at 
  995. resizing first.
  996.  
  997. resize? need only check that the mouse is touching the 
  998. item’s direct manipulation “handles.” It must also 
  999. deny resizing if the item is in a palette. For our 
  1000. purposes, the handles will be defined to be a 
  1001. rectangular frame around the bounds of the item, inset 
  1002. by a slop amount.
  1003.  
  1004. (defmethod resize? ((item draw-item) current-mouse-loc)
  1005.   (when (neq (type-of (view-window item)) 'PALETTE)    
  1006.     (rlet ((handles-rect :rect
  1007.                          :topleft (view-position item)
  1008.                          :bottomright (add-points
  1009.                                        (view-position item)
  1010.                                        (view-size item))))
  1011.       (inset-rect handles-rect *slop-in-pixels* *slop-in-pixels*)
  1012.       (not (point-in-rect-p handles-rect current-mouse-loc)))))
  1013.  
  1014. The when statement checks whether the item is in a 
  1015. palette. Note the use of the Common Lisp type-of 
  1016. function to determine the class of the window 
  1017. containing the item! rlet is a kind of let provided by 
  1018. MCL that allows one to bind a variable to a Macintosh 
  1019. record (that is, a pointer to a record in the Macintosh 
  1020. heap). The rlet is efficient because it allocates the 
  1021. record in the stack instead of calling the Memory 
  1022. Manager. We bind a rectangle record (the keyword :rect 
  1023. specifies this) to the variable handles-rect. The 
  1024. keywords :topleft and :bottomright allow us to supply 
  1025. the item’s position, which is deduced using view-
  1026. position and view-size methods. inset-rect is one of 
  1027. the QuickDraw utility functions available with MCL 
  1028. that enables us to inset the rectangle, and *slop-in-
  1029. pixels* is one of our globals that we bind to a number 
  1030. like 4. Finally, if the mouse location is not in the inset 
  1031. rectangle, then the user wants to resize.
  1032.  
  1033. The resize method illustrates the use of direct stack-
  1034. based toolbox calls: these are the functions whose 
  1035. names are prefixed by the underscore (_) character, 
  1036. like _InvertRect. In these calls, you can only pass a 
  1037. pointer, a single word, or a double word. You must take 
  1038. all the precautions you would if you were calling the 
  1039. traps from assembler. As a matter of fact, you are at 
  1040. assembler level when you make these calls: you can 
  1041. pass pointers or handles or a long number in a double 
  1042. word: you’re the boss. 
  1043. [ A numbersign (#) is added so the trap (that's what toolbox
  1044.   calls are called) will be autoloaded on first use. ]
  1045.  
  1046. Another important feature of Common Lisp to observe 
  1047. in resize is the unwind-protect construct. This 
  1048. enables you to protect an expression (in this case, the 
  1049. long expression enclosed within the loop) in case 
  1050. there’s an error during its execution. The unwind-
  1051. protect guarantees that the expressions following the 
  1052. protected expression will be executed despite the 
  1053. error. We thus ensure that all the regions created for 
  1054. resize will be disposed of even if the routine crashes.
  1055.  
  1056. The drag method creates a region around the bounds of 
  1057. the thing to be dragged and calls the function drag-
  1058. inverted-region, which enables drags to occur 
  1059. between as well as within windows and returns an 
  1060. offset to where the item was dragged. If the 
  1061. destination window is the same as the window where 
  1062. the drag started and the window is a palette, we want 
  1063. to do nothing since we can’t have a drag within a 
  1064. palette. Otherwise, we adjust the position of the item 
  1065. using set-view-position. If the move is to another 
  1066. window, then we use move-item-to-window. The 
  1067. latter will interpret a drag as a clone of the item if 
  1068. the drag started on a palette and ended in a draw-
  1069. dialog window, or else it will interpret it as a simple 
  1070. drag. move-item-to-window uses the MCL view 
  1071. methods add-subviews and remove-subviews to add 
  1072. and remove the items from the source and destination 
  1073. windows. If there’s a clone, the function clone-draw-
  1074. item is called and the item is added to the destination 
  1075. window. move-item-to-window ends by selecting the 
  1076. destination window.
  1077.  
  1078. Finally, we must define some useful draw-item 
  1079. subclasses. Since draw-item is a subclass of MCL’s 
  1080. dialog-item class, any dialog-item object of MCL 
  1081. can be a draw-item. For example, to define a check box 
  1082. draw-item:
  1083.  
  1084. (defclass check-box (draw-item check-box-dialog-item)
  1085.   ())
  1086.  
  1087. This creates a class from which check boxes can be 
  1088. instantiated. Note the multiple inheritance: check-box 
  1089. inherits both from draw-item and from the predefined 
  1090. MCL check-box-dialog-item classes. check-box-
  1091. dialog-item (and the other MCL dialog items) have 
  1092. methods defined for drawing the radio buttons and 
  1093. performing the default actions associated with them. 
  1094. The drawing of the object is done by the method view-
  1095. draw-contents, which gets called whenever a draw 
  1096. item needs to be drawn by virtue of the fact that 
  1097. draw-item is a subclass of dialog-item. By this same 
  1098. device, we can define the default mouse-down event 
  1099. handlers for draw items to include the mouse-down 
  1100. behavior of the dialog items simply by:
  1101.  
  1102. (defmethod mouse-down ((item draw-item) where)
  1103.   (dialog-item-action item))
  1104.  
  1105. dialog-item-action is the MCL method that performs 
  1106. the action of a dialog item. The method for the dialog-
  1107. item class itself does nothing. The one for the check-
  1108. box-dialog-item does the checking or unchecking of 
  1109. the box, depending on its state. And so on.
  1110.  
  1111. If we want to define a brand new kind of item, we can 
  1112. do this as follows:
  1113.  
  1114. (defclass brand-new-kind-of-item (draw-item)
  1115.   ())
  1116.  
  1117. Suppose we want it to display as a red rectangle. Then 
  1118. we must define a view-draw-contents as follows:
  1119.  
  1120. (defmethod view-draw-contents ((item brand-new-kind-of-item))
  1121.   (with-port (wptr item)
  1122.     (with-fore-color *red-color*
  1123.       (#_FrameRect :ptr (slot-value item 'rectangle))))
  1124.   (call-next-method))
  1125.  
  1126. If we want it to beep when someone clicks on it, then 
  1127. we define something like:
  1128.  
  1129. (defmethod mouse-down ((item brand-new-kind-of-item) where)
  1130.   (ed-beep))
  1131.  
  1132. The complete source code provides a detailed view of 
  1133. how this works and looks together.
  1134.  
  1135.  
  1136. end of file All about the Mini-App.text
  1137. -----------------------------------------------
  1138.